package dev.ece.core.cells.world;

import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.lwjgl.glfw.GLFW;

import dev.ece.core.cells.CellGroup;
import dev.ece.core.cells.model.UI;
import dev.ece.core.engine.EngineEvent.Pointer;
import dev.ece.core.sprite.Component;
import dev.ece.util.lib.Matrix4f;
import dev.ece.util.lib.Vector3f;
import dev.ece.util.tools.Camera;
import dev.ece.util.tools.Projection;
import dev.ece.util.tools.Screen;

public class World2D extends CellGroup<Component<?, ? extends UI<?>>> {

	/**
	 * 
	 */
	private static final long serialVersionUID = 2694503360117016141L;
	
	private static final ThreadLocal<World2D> WORLDLOCAL = new ThreadLocal<World2D>();
	
	private Map<Integer, Pointer> buttons = Collections.synchronizedMap(new ConcurrentHashMap<Integer, Pointer>());
	
	private Camera camera = new Camera();
	
	private Screen screen;
	
	private Projection projection;
	
	public World2D(Screen screen, Projection projection) {
		if(screen == null || projection == null) {
			throw new NullPointerException();
		}
		this.screen = screen;
		this.projection = projection;
		this.camera.setPosition(0, 0, 2f);
	}
	
	public World2D(Screen screen) {
		this(screen, new Projection().set2DScreen(screen));
	}

	public synchronized void logic() {
		if(this.isVisible() && this.isInitialized() && this.isLogic()) {
			WORLDLOCAL.set(this);
			super.logic(this.getCamera());
			WORLDLOCAL.remove();
		}
	}

	public synchronized void render() {
		if(this.isVisible() && this.isInitialized()) {
			WORLDLOCAL.set(this);
			this.glPushScissor(this.screen.getX(), this.screen.getY(), this.screen.getWidth(), this.screen.getHeight());
			this.screen.glViewPort();
			this.projection.glProjection2D();
			this.camera.lookAt();
			super.render(this.camera);
			this.glPopScissor();
			WORLDLOCAL.remove();
		}
	}
	
	public static final World2D currentWorld2D() {
		return WORLDLOCAL.get();
	}

	public final Vector3f getPointer(float winX, float winY) {
		Matrix4f project = new Matrix4f();
		project.identity();
		this.projection.glProjection2D(project);
		Matrix4f view = new Matrix4f();
		view.identity();
		this.camera.lookAt(view);
		return Matrix4f.gluUnProject(winX, winY, 0, project, view, screen);
	}
	
	public synchronized boolean mouseButtonEvent(long window, int button, int action, int mods, Pointer pointer) throws Exception {
		Vector3f position = getPointer(pointer.getX(), pointer.getY());
		if(action == GLFW.GLFW_PRESS) {
			buttons.put(button, pointer);
			//按下
			return mouseButtonDown(button, position.getX(), position.getY());
		} else if(action == GLFW.GLFW_RELEASE) {
			Pointer old = buttons.get(button);
			if(old != null) {
				boolean isClick = false;
				boolean isMouseUp = mouseButtonUp(button, position.getX(), position.getY());
				if(old.close(pointer)) {
					isClick = mouseButtonClick(button, position.getX(), position.getY());
				}
				//抬起
				return isMouseUp || isClick;
			}
		}
		return false;
	}

	public synchronized boolean mouseMoveEvent(long window, Pointer pointer) throws Exception {
		Vector3f position = getPointer(pointer.getX(), pointer.getY());
		return mouseMove(position.getX(), position.getY());
	}

	public synchronized boolean mouseScrollEvent(long window, double xoffset, double yoffset, Pointer pointer) throws Exception {
		Vector3f position = getPointer(pointer.getX(), pointer.getY());
		return mouseScroll(xoffset, yoffset, position.getX(), position.getY());
	}
	
	public synchronized boolean mouseMove(float x, float y) {
		if(this.isVisible()) {
			Iterator<Component<?, ? extends UI<?>>> it = this.reverseIterator();
			while(it.hasNext()) {
				if(it.next().mouseMove(x, y)) {
					return true;
				}
			}
		}
		return false;
	}

	public synchronized boolean mouseButtonDown(int button, float x, float y) {
		if(this.isVisible()) {
			Iterator<Component<?, ? extends UI<?>>> it = this.reverseIterator();
			while(it.hasNext()) {
				if(it.next().mouseButtonDown(button, x, y)) {
					return true;
				}
			}
		}
		return false;
	}

	public synchronized boolean mouseButtonUp(int button, float x, float y) {
		if(this.isVisible()) {
			Iterator<Component<?, ? extends UI<?>>> it = this.reverseIterator();
			while(it.hasNext()) {
				if(it.next().mouseButtonUp(button, x, y)) {
					return true;
				}
			}
		}
		return false;
	}

	public synchronized boolean mouseButtonClick(int button, float x, float y) {
		if(this.isVisible()) {
			Iterator<Component<?, ? extends UI<?>>> it = this.reverseIterator();
			while(it.hasNext()) {
				if(it.next().mouseButtonClick(button, x, y)) {
					return true;
				}
			}
		}
		return false;
	}

	public synchronized boolean mouseScroll(double xoffset, double yoffset, float x, float y) {
		if(this.isVisible()) {
			Iterator<Component<?, ? extends UI<?>>> it = this.reverseIterator();
			while(it.hasNext()) {
				if(it.next().mouseScroll(xoffset, yoffset, x, y)) {
					return true;
				}
			}
		}
		return false;
	}
	
	public Camera getCamera() {
		return camera;
	}

	public Screen getScreen() {
		return screen;
	}

	public void setScreen(Screen screen, Projection projection) {
		this.screen = screen;
		this.projection = projection;
	}
	
	public void setScreen(Screen screen) {
		setScreen(screen, new Projection().set2DScreen(screen));
	}

}
